# -*- coding: utf-8 -*-
# Purpose: HydroPlatform commands module
# Created: 11/26/2008
# Copyright (c) 2008,2009 by University College London
# Authors:
# Didrik Pinte <dpinte@dipole-consulting.com>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with HydroPlatform for details.

import logging
import os
import sqlalchemy
import wx

import Thuban
from Thuban import _
from Thuban.UI.command import registry, Command, ToolCommand
from Thuban.UI.mainwindow import make_check_current_tool


from hydroplatform.model import database as db
from hydroplatform.model.layer import NetworkLayer
from hydroplatform.model.network import Project
from hydroplatform.model import nodetypes
from hydroplatform.model import units
from hydroplatform.model import networkfun
from hydroplatform.ui.tools import IdentifyObjectTool, MoveNodeTool, \
     AddNodeTool, AddLinkTool, DeleteObjectTool
from hydroplatform.ui.node import ObjectPopupMenu
from hydroplatform.ui.objecteditordialog import NonVisibleObjectsEditor
from hydroplatform.ui.objecttypeeditordialog import ObjectTypeEditorDialog
from hydroplatform.ui.projecteditordialog import TraitedProjectEditorDialog, ProjectHandler
from hydroplatform.ui.object_importer import ProjectObjectList, ChooseProject
from hydroplatform.ui.export import ExportFrame
from hydroplatform.ui.renderer import getBitmapForNodeType
from hydroplatform.ui.dbdialog import ChooseDbConnection, DbDialogHandler
from hydroplatform.ui.session import has_project, has_network, remove_loaded_networks

from hydroplatform.ui.join import JoinFrame
from hydroplatform.ui.join import NetworkJoinerEditor


def connect_to_db(context):
    print "Context : %s" % context
    dialog = ChooseDbConnection(thcontext = context)
    if dialog.configure_traits(kind="modal"):
        units.create_default_units(context.session.dbsession)
        #Evgenii commented out below because new dbs do not have projects or networks and cause errors
        #open_project(context)
        #open_network(context)
    else:
        logging.info('No dbsession opened')
    

def select_network(project):
    '''
    Returns a network selected by the user
    '''
    network_names  = [n.name for n in project.networks]
    dialog = wx.SingleChoiceDialog(None, 
                                  message="Choose a network", 
                                  caption="Existing networks",
                                  choices = network_names)
    if dialog.ShowModal() == wx.ID_OK:
        # save the loaded project to session
        network_name = dialog.GetStringSelection()
        for network in project.networks:
            if network.name == network_name:
                return network
    return None
 
def join_network(context, network=None):
	project = context.session.hpproject
	networks = [n.name for n in project.networks]
	joinFrame = JoinFrame(context.mainwindow, context)
 
def open_network(context, network=None):
    """
    Opens a network and add it has NetworkLayer in the map layers
    
    FIXME : does not allow two networks to be opened at the same time !
    """
    project = context.session.hpproject
    if has_network(context) is True:
        remove_loaded_networks(context)
    if network is None:
        # load a network from UI
        network = select_network(project) 
    if network is not None: 
        map = context.mainwindow.canvas.Map()
        context.session.hpnetwork = network 
        # The following two lines run through all the links and reset the names to the 
        # concatenated names of the nodes
        for i in network.links:
          i.name = i.start.name+'_'+i.end.name
        context.session.dbsession.commit()
        layer = NetworkLayer(network, projection=project.GetProjection())
        if map.projection is None:
            map.SetProjection(layer.projection)
        has_layers = map.HasLayers()
        map.AddLayer(layer)
        # FIXME : this should be done using event subscription
        # add the right click menu management to canvas
        pp = ObjectPopupMenu(context)
        context.mainwindow.canvas.Bind(wx.EVT_RIGHT_UP, pp.load_popup_menu)
        if not has_layers:
            # if we're adding a layer to an empty map, fit the
            # new map to the window
            context.mainwindow.canvas.FitMapToWindow()
        


                
def new_network(context):
    dbsession = context.session.dbsession
    project = context.session.hpproject
    # ask the new network name to the user
    dialog = wx.TextEntryDialog(None,
                "Network name",
                "New network")
    if dialog.ShowModal() == wx.ID_OK:
        name = dialog.GetValue()
        #try:
        network = db.Network(name, project)
        context.session.dbsession.add(network)
        context.session.dbsession.commit()
        open_network(context, network)
        #except Exception, e:
        #    context.session.dbsession.rollback()
        #    raise e
    

def label_network(context):
    '''
    de/activate the labelling on a network
    '''
    # if multiple loaded NetworkLayer --> choose one
    cmap = context.mainwindow.canvas.map
    layers = [ layer for layer in cmap.Layers() \
               if isinstance(layer, NetworkLayer)]
    if len(layers) > 1:
        # choose one
        pass
    elif len(layers) == 0 :        
        return
    else : 
        layer = layers[0]
        
    # toggle the labelling
    if layer.labeled is True:
        layer.labeled = False
    else:
        layer.labeled = True
        
    context.session.label_activated = layer.labeled
    
    # redraw map
    context.mainwindow.canvas.full_redraw() 

def is_label_activated(context):
    try:
        return context.session.label_activated
    except:
        return False
    
def save_network(context):
    raise NotImplementedError()


def saveas_network(context):
    dialog = wx.TextEntryDialog(None,
                "Network name",
                "New network",
                defaultValue="%s_new" % context.session.hpnetwork.name)
    if dialog.ShowModal() == wx.ID_OK:
        new_name = dialog.GetValue()
        networkfun.save_network_as(context.session.dbsession, context.session.hpnetwork, new_name)
    
def rename_network(context):
    dialog = wx.TextEntryDialog(None,
                "Network name",
                "New network",
                defaultValue=context.session.hpnetwork.name)
    if dialog.ShowModal() == wx.ID_OK:
        context.session.hpnetwork.name = dialog.GetValue()
        context.session.dbsession.commit()
        cmap = context.mainwindow.canvas.map
        for layer in cmap.Layers() :
            if isinstance(layer, NetworkLayer):
                if layer.network is context.session.hpnetwork:
                    layer.SetTitle(layer.network.name)
                       
        
def delete_network(context):
    dbsession = context.session.dbsession
    network = select_network(context.session.hpproject)
    if network is not None:
        if context.session.hpnetwork == network:
            networkfun.delete_network(dbsession, network)
            del(network)
            wx.MessageBox(u"Network deleted sucessfully.", 
                          u"Delete network")
        else:
            wx.MessageBox(u"Network must be loaded to be deleted.",
                          u"Delete network")


def network_edit(context):
  for i in range(1000):
    print "NETWORK_EDIT!!!!!!!!!!!!!!!!!!!"



def new_project(context):
    dialog = wx.TextEntryDialog(None,
                "Create a new project",
                "New project")
    if dialog.ShowModal() == wx.ID_OK:
        name = dialog.GetValue()
        #try:
        project = Project(name, None)
        # show project editor and force user to set the time horizon 
        # of the project
        teditor = TraitedProjectEditorDialog(
                     context=context, 
                     project=project,
                     punits = units.get_unit_categories( \
                         context.session.dbsession))
        if teditor.configure_traits(kind="modal", handler=ProjectHandler()) :
            # open the project
            context.session.dbsession.add(teditor.project)
            context.session.dbsession.commit()
            # check and register default node types for the loaded project
            nodetypes.register_default_type(context.session.dbsession, project)
            # open the newly created project
            open_project(context, project)
            
def open_project(context, project=None):
    dbsession = context.session.dbsession
    nodeTypeMenu = context.application.get_nodetype_menu(context)
    
    if has_project(context) is True:
        # close the project
        # remove the network 
        remove_loaded_networks(context)
        # remove the menus
        nodeTypeMenu.cleanup() 
        # cleanup old references
        context.session.hpnetwork = None
        context.session.hpproject = None
        
    if project is None:
        try:
            projects = dbsession.query(Project).all()    
            if len(projects) == 1:
                context.mainwindow.RunMessageBox(_('Open project'), 'Only one project in database. Loading %s' % projects[0].name)
                project = projects[0]
            else :
                project = select_project(projects)
        except sqlalchemy.exceptions.DBAPIError, e:
            context.mainwindow.RunMessageBox(_('Database error'),
                _('''There was a problem with your database structure.\
                     Database schema upgrade/migration is not supported at the moment.\
                     Workaround is to upgrade your database schema manually or start with a new one
                    \n %s
                ''' % e)) 
    if project is not None:
        context.session.hpproject = project
        context.mainwindow.update_title(project.name)         
        # update menu according to the loaded project
        nodeTypeMenu.load_from_project(project)
        # load units from the project
        units.load_units(context.session.dbsession, project)

def select_project(projects):
    '''
    Select a project from the connected database
    '''
    project_names = [p.name for p in projects]
    dialog = wx.SingleChoiceDialog(None, 
                                  message="Choose a project", 
                                  caption="Existing projects",
                                  choices = project_names)
    res = dialog.ShowModal()
    if res == wx.ID_OK:
        # save the loaded project to session
        project_name = dialog.GetStringSelection()
        for project in projects:
            if project.name == project_name:
                return project
    else:
        return None
            
            
def edit_project(context):

    # LOOK AT THIS FUCKING _DISASTER_
    # MARK
    # problem here as it seems that when the dates are changed for project, end 
    # dates in other places dont change
    # Note: At the end of this method, the context.session.hpproject.horizon_to, which is a 
    # datetime object, is actually correct

    # Here is the problem:
    # for any node (or link), call it n, in context.session.hpproject.nodes,
    # n.type.fields[0].attribute_type.tod is NOT changed (in this case fields[0] is a TSeries 
    # Note: there is a field.attribute_type.update_time_horizon function in a TS field
    # THIS IS HORRIBLE
    # This means that there needs to be a section somewhere which updates all the dates
    # in all the relevant fields.
    # Question: what about the timeframe of the networks in the project?
    # Answer: There is no date on a network

    # Where are the dates?   <-------- That's a really good question
    # Project: There is one for the project (which is correct)
    # Network: There is not time
    # Time Series: There are certainly dates horizons in TS fields in links and nodes
    # Time Series: has an update_time_horizon(project) function
    # Parameters: There is not date....... unless it's a unit?? Is there a limit on allowable dates. 
    # cont..There should be. 
    # Seasonal Parameters: DO have dates, has an update_time_horizon(project) function
    # Refs: do not have dates
    # Table: don't have dates

    # Note: there is a function in networkfun.py called project_time_horizon_changed

    # The project_time_horizon_changed function is called. However, for some reason, it's not doing it's job
    
    teditor = TraitedProjectEditorDialog(
                     context=context, 
                     project=context.session.hpproject,
                     punits = units.get_unit_categories(context.session.dbsession,
                                                        context.session.hpproject))
    if teditor.configure_traits(kind="modal", handler=ProjectHandler()) :
        logging.info("Project saved after edition")
        context.session.dbsession.commit()
    else:
        logging.info("Project not saved after edition")
        context.session.dbsession.rollback()


def saveas_project(context, p_name=None):
    """ Save current project to a new one.
    
    Parameters:
    -----------
    
    context : Thuban context
        Initial Thuban context with a loaded Project
    p_name : str
        New project name to be used (asked from UI if None)
    
    Requires transferring nodetypes, linktypes, nodes, links, nodevalues 
    and linkvalues
    """    
    if p_name is None:
        # ask the new network name to the user
        dialog = wx.TextEntryDialog(None,
                    "Project name",
                    "Save project as")
        if dialog.ShowModal() == wx.ID_OK:
            p_name= dialog.GetValue()
        else:
            return
    fp = context.session.hpproject
    dbsession = context.session.dbsession
    # FIXME : should be extracted to networkfun module
    try:
        project = Project(p_name, fp.description, fp.GetProjection())    
        dbsession.add(project)
        dbsession.flush()
        fromto_dict = dict()
        for name in ['nodes', 'links', 'ntypes', 'ltypes']:
            fromto_dict[name] = dict()
        for ntype in fp.nodetypes:
            tid= networkfun.copy_objecttype_toproject(dbsession, project, ntype)
            fromto_dict['ntypes'][ntype.id] = tid
        dbsession.flush()
        ltype_dict = dict()
        for ltype in fp.linktypes:
            tid = networkfun.copy_objecttype_toproject(dbsession, project, ltype)
            fromto_dict['ltypes'][ltype.id] = tid
        dbsession.flush()
        node_dict = dict()
        for node in fp.nodes:
            tid = networkfun.copy_object_toproject(dbsession, project, node)
            fromto_dict['nodes'][node.id] = tid
        dbsession.flush()
        link_dict = dict()
        for link in fp.links:            
            tid = networkfun.copy_object_toproject(dbsession, project, link)
            fromto_dict['links'][link.id] = tid
        dbsession.flush()
        for network in fp.networks:
            networkfun.copy_network_toproject(dbsession, project, \
                                              network, fromto_dict)
        dbsession.commit()
        dbsession.refresh(project)
        # open this new project
        open_project(context, project)
    except sqlalchemy.exc.DatabaseError, e:
        dbsession.rollback()
        raise e
        

def rename_project(context):
    raise NotImplementedError()

def delete_project(context):
    dbsession = context.session.dbsession
    project = select_project(dbsession.query(Project).all())
    if project is not None:
        if has_project(context) is False or \
           (has_project(context) is True and \
           context.session.hpproject.id != project.id) :
            if has_project(context) is False:
              context.session.hpproject = project
            networkfun.delete_project(dbsession, project)
            wx.MessageBox(u"Project deleted sucessfully", 
                  u"Project network")
        else:
            wx.MessageBox(u"You may not delete this project as it is currently open", 
                  u"Project network")

def export_network(context):
    exportFrame = ExportFrame(context.mainwindow, context)
    exportFrame.Show()

def new_type(context):
    raise NotImplementedError()

def list_types(context):
    raise NotImplementedError()

def add_existing_node(context):    
    # allow selection of a node existing in the project but not in this network
    dbsession = context.session.dbsession
    project = context.session.hpproject
    network = context.session.hpnetwork
    nodes = [node.name for node in project.nodes if node not in network.nodes]
    dialog = wx.SingleChoiceDialog(None, 
                                  message="Choose a node", 
                                  caption="Existing nodes not in network",
                                  choices = nodes)
    if dialog.ShowModal() == wx.ID_OK:
        # save the loaded project to session
        node_name = dialog.GetStringSelection()
        for node in project.nodes:
            if node.name == node_name:
                # add node to network
                network.nodes.append(node)
                dbsession.commit()

editFrame = None
def edit_types(context):
    global editFrame
    if editFrame is None:
        editFrame = ObjectTypeEditorDialog(context.mainwindow.canvas, context)
        editFrame.Show()
    else:
        # test if editFrame has not been destroyed
        if editFrame:
            # raise it
            editFrame.Raise()
        else:
            # create a new frame
            editFrame = ObjectTypeEditorDialog(context.mainwindow.canvas, context)
            editFrame.Show()
 

def edit_non_visible_types(context):
    typesdict = dict()
    for ntype in context.session.hpproject.nodetypes:
        if ntype.visible_on_map is False:
            typesdict[ntype.name] = ntype
    editor = NonVisibleObjectsEditor(node_types=typesdict, 
                                         context=context)
    editor.edit_traits()
                
def add_node_with_type(context, *args):
    assert isinstance(args[0], db.NodeType)
    canvas = context.mainwindow.canvas
    canvas.SelectTool(AddNodeTool(canvas, context, args[0]))

def identify_node_tool(context):
    canvas = context.mainwindow.canvas
    canvas.SelectTool(IdentifyObjectTool(canvas, context))
    
def move_node_tool(context):
    canvas = context.mainwindow.canvas
    canvas.SelectTool(MoveNodeTool(canvas, context))

def add_link_tool(context):
    canvas = context.mainwindow.canvas
    canvas.SelectTool(AddLinkTool(canvas, context))

def delete_node_tool(context):
    canvas = context.mainwindow.canvas
    canvas.SelectTool(DeleteObjectTool(canvas, context))
    
    
def import_existing_types(context):
    # Choose a project
    t = ChooseProject( session = context.session.dbsession,
                      project = context.session.hpproject)
    ret = t.configure_traits(view=t.get_view(),
                             kind="modal")
    if ret is True:
        # If project choosen, show the object types for this project
        p = ProjectObjectList(context = context,
                              session = context.session.dbsession,
                              project = context.session.hpproject,
                              from_projectid=t.projectid)
        p.configure_traits(view=p.get_view(), kind="live")
